package com.yao.mylock.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yao.mylock.entity.MysqlLock;
import com.yao.mylock.entity.Stock;
import com.yao.mylock.mapper.LockMapper;
import com.yao.mylock.mapper.StockMapper;
import com.yao.mylock.service.StockService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author yao
 * @since 2022-09-06
 */
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements StockService {

    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    //@Autowired
    //private CuratorFramework curatorFramework;

    @Autowired
    private LockMapper lockMapper;

    private ReentrantLock lock = new ReentrantLock();

    @Override
    //public synchronized void deduct() {
    public void deduct() {
        lock.lock();
        //1.查询库存
        try {
            Stock stock = baseMapper.selectOne(new QueryWrapper<Stock>().eq("product_code", 1001));
            //2.判断库存是否充足
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                //3.更新库存
                baseMapper.updateById(stock);
            }
        } finally {
            lock.unlock();
        }
        //System.out.println("当前库存为：" + stock.getCount());

        //一个sql语句不加锁在集群模式下也可以解决超卖问题
        //stockMapper.sole(1001, 1);
    }

    //使用mysql悲观锁  select .. for update
    @Transactional//开启事务
    public void deduct2() {
        //1.查询库存
        List<Stock> stocks = this.stockMapper.queryStock(1001);
        //取第一个（省略仓库判断）
        Stock stock = stocks.get(0);
        //2.判断库存是否充足
        if (stock != null && stock.getCount() > 0) {
            stock.setCount(stock.getCount() - 1);
            //3.更新库存
            this.stockMapper.updateById(stock);
        }
    }

    //使用mysql乐观锁
    //@Transactional  使用乐观锁不能加事务，不然会导致等待时间过久报连接超时
    public void deduct3() {
        //1.查询库存
        List<Stock> stocks = this.stockMapper.selectList(new QueryWrapper<Stock>().eq("product_code", 1001));
        //取第一个（省略仓库判断）
        Stock stock = stocks.get(0);
        //2.判断库存是否充足
        if (stock != null && stock.getCount() > 0) {
            stock.setCount(stock.getCount() - 1);
            Integer version = stock.getVersion();
            stock.setVersion(version + 1);
            //3.更新库存
            if (this.stockMapper.update(stock, new QueryWrapper<Stock>().eq("id", stock.getId()).eq("version", version)) == 0) {
                try {
                    //递归方法设置一定的休眠时间防止栈溢出
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新失败重试
                this.deduct3();
            }
        }
    }

    //使用redis 不加锁会出错，使用乐观锁性能差
    public void deduct4() {
        /*//一、不使用锁 会出现并发问题
        //1.查询库存
        String stock = redisTemplate.opsForValue().get("stock");
        //2.判断库存是否充足
        if (!StringUtils.isEmpty(stock)) {
            //3.更新库存
            //redisTemplate.opsForValue().decrement("stock", 1);
            Integer st = Integer.valueOf(stock);
            if (st > 0) {
                //3.更新库存
                redisTemplate.opsForValue().set("stock",String.valueOf(st-1));
            }
        }*/

        //二、使用乐观锁 watch
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //监听watch
                operations.watch("stock");
                //1.查询库存
                String stock = operations.opsForValue().get("stock").toString();
                //2.判断库存是否充足
                if (!StringUtils.isEmpty(stock)) {
                    Integer st = Integer.valueOf(stock);
                    if (st > 0) {
                        //开启事务
                        operations.multi();
                        //3.更新库存
                        operations.opsForValue().set("stock", String.valueOf(st - 1));
                        //执行事务
                        List exec = operations.exec();
                        //根据事务返回结果判断是否执行成功
                        if (exec == null || exec.size() == 0) {
                            //结果为空，失败重试
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            deduct4();
                        }
                        //成功则返回exec
                        return exec;
                    }
                }
                return null;
            }
        });
    }

    //使用redis分布式锁，可重入和自动续期比较复杂，不加代码演示了，了解原理即可！
    public void deduct5() {
        String uuid = UUID.randomUUID().toString();
        //获取锁并设置过期时间防止死锁
        //通过一行代码保证获取锁和设置过期时间的原子性
        while (!this.redisTemplate.opsForValue().setIfAbsent("stocklock", uuid,3, TimeUnit.SECONDS)) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //重试获取锁
            this.deduct5();
        }

        try {
            //1.查询库存
            String stock = redisTemplate.opsForValue().get("stock");
            //2.判断库存是否充足
            if (!StringUtils.isEmpty(stock)) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.更新库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(st - 1));
                }
            }
        } finally {
            //方式一：判断是不是自己的锁再释放
            /*if (StringUtils.equals(redisTemplate.opsForValue().get("stocklock"),uuid)){
                //释放锁
                this.redisTemplate.delete("stocklock");
            }*/

            //方式二：通过lua脚本保证判断和删除的原子性
            String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1])" +
                    "else return 0 end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("stocklock"), uuid);
        }
    }

    //使用Redisson框架实现分布式锁（自带可重入和自动续期）
    public void deduct6() {
        //通过框架获取锁
        RLock lock = redissonClient.getLock("lock");
        lock.lock();

        try {
            //1.查询库存
            String stock = redisTemplate.opsForValue().get("stock");
            //2.判断库存是否充足
            if (!StringUtils.isEmpty(stock)) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.更新库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(st - 1));
                }
            }

            //为了测试自动续期，增加了业务处理时间
            /*try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        } finally {
            lock.unlock();
        }
    }
    //使用curator框架实现分布式锁（性能低下！）
    public void deduct7() {
        /*//可重入锁：InterProcessMutex 不可重入锁：InterProcessSemaphoreMutex
        InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/curator/locks");
        try {
            //加锁`
            mutex.acquire();

            //1.查询库存
            String stock = redisTemplate.opsForValue().get("stock");
            //2.判断库存是否充足
            if (!StringUtils.isEmpty(stock)) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.更新库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(st - 1));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //解锁
            try {
                mutex.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }*/


    }
    //使用mysql实现分布式锁（性能低下！）:利用mysql唯一索引原理
    public void deduct8() {
        try {
            //加锁
            MysqlLock lock = new MysqlLock();
            lock.setMylock("lock");
            lockMapper.insert(lock);

            //1.查询库存
            String stock = redisTemplate.opsForValue().get("stock");
            //2.判断库存是否充足
            if (!StringUtils.isEmpty(stock)) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.更新库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(st - 1));
                }
            }

            //解锁
            lockMapper.deleteById(lock.getId());
        } catch (Exception e) {
            //e.printStackTrace();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            this.deduct8();
        }
    }
}
